#include "znx_http_request.h"

static void
znx_http_try_switch_request_header_buf(znx_http_request_t *request, size_t buf_size)
{
    znx_buf_t                   *header_buf;
    znx_http_readline_sm_t      *readline_sm;
    size_t                      len = 0;

    buf_size += ZNX_HTTP_MIN_WRITEABLE_SIZE;
    header_buf = request->last_buf;
    readline_sm = &request->readline_sm;

    // has unparsed data, no need switch buf
    if (header_buf && header_buf->end != header_buf->pos) {
        return;
    }

    // avoid writeing in a small space
    if (header_buf && header_buf->data_end - header_buf->end > ZNX_HTTP_MIN_WRITEABLE_SIZE) {
        return;
    }

    if (header_buf) {
        len = (size_t)(header_buf->end - readline_sm->line.data);
    }

    header_buf = znx_buf_alloc_ext(buf_size);
    request->last_buf = header_buf;
    znx_buf_chain_enqueue(request->header_chain, header_buf, ZNX_FALSE);

    if (len == 0) {
        return;
    }

    // copy and reset readline_sm
    znx_strncpy(header_buf->end, readline_sm->line.data, len);
    header_buf->end += len;
    znx_http_readline_sm_reset(readline_sm);
}


static znx_result_t
znx_http_do_read_request_header(znx_http_request_t *request)
{
    znx_buf_t               *header_buf;
    znx_http_readline_sm_t  *readline_sm;
    znx_http_header_t       tmp;
    znx_result_t            res;
    znx_http_core_conf_t    *cf;
    znx_http_header_t       *header;
    size_t                  n;
    znx_http_conn_t         *hc;

    cf = request->cf;
    readline_sm = &request->readline_sm;
    hc = request->hc;

    for ( ;; ) {
        znx_http_try_switch_request_header_buf(request, cf->request_header_buf_size);

        header_buf = request->last_buf;

        // no more data, read it.
        if (header_buf->pos == header_buf->end) {
            n = (size_t)(header_buf->data_end - header_buf->end);
            res = znx_http_conn_read(request->hc, header_buf->end, n);
            if (res == ZNX_AGAIN) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "read request header again");
                return ZNX_AGAIN;
            }

            if (res == 0) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "client close write in read request header");
                return ZNX_HTTP_CLIENT_CLOSE_IN_READ_HEADER;
            }

            if (res < 0) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "read request header failed, err: %d", res);
                return ZNX_HTTP_READ_HEADER_FAILED;
            }

            header_buf->end += res;
        }

        // read line
        res = znx_http_readline(readline_sm, header_buf);
        if (res == ZNX_AGAIN) {
            continue;
        }

        if (res != ZNX_SUCCESS) {
            ZNX_HTTP_LOG_DEBUG(hc->hlog, "found big header line");
            return ZNX_HTTP_BIG_HEADER_LINE;
        }

        request->header_size += readline_sm->line.len;

        if (request->header_size > cf->max_request_header_size) {
            ZNX_HTTP_LOG_DEBUG(hc->hlog, "client request header is too big");
            return ZNX_HTTP_REQUEST_HEADER_TOO_LARGER;
        }

        if (znx_http_empty_line(readline_sm)) {
            if (!request->parse_rl_done) {
                return ZNX_HTTP_INVALID_REQUEST_LINE;
            }

            request->parse_rh_done = 1;
            return ZNX_SUCCESS;
        }

        if (!request->parse_rl_done) {
            res = znx_http_parse_request_line(request);
            if (res != ZNX_SUCCESS) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "parse request line failed, err: %d", res);
                return res;
            }

            request->parse_rl_done = 1;
            continue;
        }

        bzero(&tmp, sizeof(znx_http_header_t));
        res = znx_http_parse_header_line(&request->readline_sm.line, &tmp);
        if (res != ZNX_SUCCESS) {
            if (cf->ignore_invalid_headers) {
                request->invalid_headers++;
                continue;
            }
        }

        header = znx_pool_calloc(request->pool, sizeof(znx_http_header_t));
        header->key = tmp.key;
        header->val = tmp.val;

        znx_http_headers_add(&request->headers, header);
    }
}


static znx_result_t
znx_http_request_process_headers(znx_http_request_t *request)
{
    znx_http_header_t   *header;
    size_t              index;
    znx_bool_t          has_content_length_header = ZNX_FALSE;

    header = znx_http_headers_get(&request->headers, &znx_http_host_hdr_name, 0);
    if (header == NULL) {
        request->host = header->val;
    }

    header = znx_http_headers_get(&request->headers, &znx_http_content_length_hdr_name, 0);
    if (header != NULL) {
        int64_t content_length = -1;
        if (!znx_str_to_int64(&header->val, &content_length) || content_length < 0) {
            return ZNX_HTTP_INVALID_CONTENT_LENGTH_HEADER;
        }

        request->content_length = content_length;
        request->content_remain = content_length;
        has_content_length_header = ZNX_TRUE;
    }

    header = znx_http_headers_get(&request->headers, &znx_http_transfer_encoding_hdr_name, 0);
    if (header != NULL) {
        if (header->val.len == ZNX_HTTP_CHUNKED_LEN &&
            znx_strncmp(header->val.data, ZNX_HTTP_CHUNKED, ZNX_HTTP_CHUNKED_LEN) == 0)
        {
            request->chunked = 1;
            // overwrite content_length.
            request->content_length = -1;
            request->content_remain = -1;
        } else if (header->val.len == ZNX_HTTP_IDENTITY_LEN &&
            znx_strncmp(header->val.data, ZNX_HTTP_IDENTITY, ZNX_HTTP_IDENTITY_LEN) == 0)
        {
            // nothing
        } else {
            return ZNX_HTTP_ERR_NOT_IMPLEMENTED;
        }
    }

    index = 0;
    header = znx_http_headers_get(&request->headers, &znx_http_trailer_hdr_name, 0);
    if (header != NULL) {
        if (!request->chunked) {
            znx_http_headers_del(&request->headers, &znx_http_trailer_hdr_name);
        } else {
            request->trailers = znx_http_trailers_create(request->pool);
            while (header) {
                znx_http_parse_trailer_header(request->pool, request->trailers, &header->val);
                index++;
                header = znx_http_headers_get(&request->headers, &znx_http_trailer_hdr_name, index);
            }
        }
    }

    if (request->http_version < ZNX_HTTP_VERSION_11) {
        request->want_close = 1;
    }

    header = znx_http_headers_get(&request->headers, &znx_http_connection_hdr_name, 0);
    if (header != NULL) {
        if (header->val.len == ZNX_HTTP_CLOSE_LEN &&
            znx_strncmp(header->val.data, ZNX_HTTP_CLOSE, ZNX_HTTP_CLOSE_LEN) == 0)
        {
            request->want_close = 1;
        }
    }

    // no request body
    if (!request->chunked && request->content_length < 0) {
        request->nobody = 1;
    }

    // the read data may contain body data or next request data.
    znx_buf_t           *body_buf;
    body_buf = request->last_buf;
    if (body_buf && body_buf->pos != body_buf->end) {
        request->body_buf = znx_buf_alloc(body_buf->data, body_buf->pos, body_buf->end);
    }

    // last_buf is the shadow of body_buf.
    // it just tracks body_buf->pos.
    request->last_buf = znx_pool_calloc(request->pool, sizeof(znx_buf_t));
    if (body_buf) {
        request->last_buf->pos = body_buf->pos;
    }

    return ZNX_SUCCESS;
}


static void
znx_http_read_request_header_handler(znx_http_conn_t *hc)
{
    znx_http_request_t      *request;
    znx_result_t            res;

    request = hc->data;

    ZNX_HTTP_LOG_INFO(hc->hlog, "http read request handler callback");

    if (hc->c->read.timedout) {
        ZNX_HTTP_LOG_DEBUG(hc->hlog, "http read request header timedout");
        hc->c->read.timedout = 0;
        request->read_error = ZNX_HTTP_READ_HEADER_TIMEOUT;
        znx_event_loop_del_timer(&hc->c->read);
        znx_http_conn_disable_read(hc);
        request->handler(request, ZNX_HTTP_READ_HEADER_TIMEOUT);
        return;
    }

    res = znx_http_do_read_request_header(request);

    // again, wait read event callback
    if (res == ZNX_AGAIN) {
        return;
    }

    // success or failed
    znx_event_loop_del_timer(&hc->c->read);
    znx_http_conn_disable_read(hc);

    if (res != ZNX_SUCCESS) {
        request->read_error = res;
        request->handler(request, res);
        return;
    }

    res = znx_http_request_process_headers(request);
    if (res != ZNX_SUCCESS) {
        request->read_error = res;
        request->handler(request, res);
        return;
    }

    request->handler(request, ZNX_SUCCESS);
}


void
znx_http_start_read_request_headers(znx_http_request_t *request,
    znx_http_request_handler_t done_handler, void *data)
{
    znx_http_conn_t         *hc;
    znx_http_core_conf_t    *cf;

    cf = request->cf;
    hc = request->hc;

    if (request->client_request) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "expect http server request");
    }

    if (request->read_error != ZNX_SUCCESS)
    {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "an error has occurred in http request");
    }

    if (request->start_read_header) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "repeat call start_read_header");
    }

    request->start_read_header = 1;
    request->handler = done_handler;
    request->data = data;

    hc->read_handler = znx_http_read_request_header_handler;
    znx_event_loop_add_timer(&hc->c->read, cf->read_request_header_timeout_ms);
    znx_http_conn_start_io(hc);

    request->readline_sm.max_line_size = cf->request_header_buf_size;
    znx_http_read_request_header_handler(hc);
}


static void
znx_http_try_switch_request_chunk_buf(znx_http_request_t *request, size_t buf_size)
{
    znx_buf_t               *body_buf;
    znx_buf_t               *last_buf;
    znx_buf_t               *new_buf;
    znx_buf_t               *tmp_buf;
    znx_http_readline_sm_t  *readline_sm;
    znx_http_chunk_sm_t     *chunk_sm;
    znx_http_core_conf_t    *cf;
    size_t                  total_size;
    size_t                  n;
    size_t                  len;

    buf_size += ZNX_HTTP_MIN_WRITEABLE_SIZE;
    body_buf = request->body_buf;
    last_buf = request->last_buf;
    readline_sm = &request->readline_sm;
    chunk_sm = request->chunk_sm;
    cf = request->cf;

    // has unparsed data, no need switch buf
    if (body_buf && body_buf->end != body_buf->pos) {
        return;
    }

    // avoid writeing in a small space
    if (body_buf && body_buf->data_end - body_buf->end > ZNX_HTTP_MIN_WRITEABLE_SIZE) {
        return;
    }

    total_size = znx_buf_chain_total_bytes(request->body_chain);
    if (total_size >= cf->max_read_request_body_size_limit) {
        return;
    }

    new_buf = znx_buf_alloc_ext(buf_size);

    if (body_buf) {
        switch (chunk_sm->state) {
        case SW_CHUNK_PRESTART:
        case SW_CHUNK_AFTER_DATA_CRCF:
            // copy and reset readline sm
            len = (size_t)(body_buf->end - readline_sm->line.data);
            znx_strncpy(new_buf->end, readline_sm->line.data, len);
            new_buf->end += len;
            znx_http_readline_sm_reset(readline_sm);
            break;

        case SW_CHUNK_DATA:
            if (last_buf == NULL ||
                last_buf->pos > body_buf->pos ||
                last_buf->pos < body_buf->start)
            {
                ZNX_HTTP_LOG_FATAL(request->hc->hlog, "unreachable, the last_buf is the shadow of body_buf");
            }

            n = (size_t)(body_buf->pos - last_buf->pos);
            if (n > 0) {
                tmp_buf = znx_buf_alloc(body_buf->data, last_buf->pos, body_buf->pos);
                znx_buf_chain_enqueue(request->body_chain, tmp_buf, ZNX_FALSE);
            }

            break;
        
        default:
            ZNX_HTTP_LOG_FATAL(request->hc->hlog, "unreachable");
            break;
        }

        znx_buf_free(body_buf);
    }

    request->body_buf = new_buf;
    if (chunk_sm->state == SW_CHUNK_DATA) {
        request->last_buf->pos = new_buf->pos;
    }
}


static void
znx_http_try_flush_request_chunk_buf(znx_http_request_t *request)
{
    znx_buf_t               *body_buf;
    znx_buf_t               *last_buf;
    znx_buf_t               *tmp_buf;
    znx_http_chunk_sm_t     *chunk_sm;
    size_t                  n;

    body_buf = request->body_buf;
    last_buf = request->last_buf;
    chunk_sm = request->chunk_sm;

    if (!body_buf) {
        return;
    }

    if (chunk_sm->state != SW_CHUNK_DATA) {
        return;
    }

    if (last_buf == NULL ||
        last_buf->pos > body_buf->pos ||
        last_buf->pos < body_buf->start)
    {
        ZNX_HTTP_LOG_FATAL(request->hc->hlog, "unreachable, the last_buf is the shadow of body buf");
    }

    n = (size_t)(body_buf->pos - last_buf->pos);

    if (n == 0) {
        return;
    }

    tmp_buf = znx_buf_alloc(body_buf->data, last_buf->pos, body_buf->pos);
    znx_buf_chain_enqueue(request->body_chain, tmp_buf, ZNX_FALSE);

    last_buf->pos = body_buf->pos;
}


static void
znx_http_try_switch_request_trailers_buf(znx_http_request_t *request, size_t buf_size)
{
    znx_buf_t               *body_buf;
    znx_buf_t               *new_buf;
    znx_http_readline_sm_t  *readline_sm;
    size_t                  len = 0;

    buf_size += ZNX_HTTP_MIN_WRITEABLE_SIZE;
    body_buf = request->body_buf;
    readline_sm = &request->readline_sm;

    // has unprased data, no need switch buf
    if (body_buf && body_buf->end != body_buf->pos) {
        return;
    }

    // avoid writeing in a small space
    if (body_buf && body_buf->data_end - body_buf->end > ZNX_HTTP_MIN_WRITEABLE_SIZE) {
        return;
    }

    if (body_buf) {
        len = (size_t)(body_buf->end - readline_sm->line.data);
    }

    new_buf = znx_buf_alloc_ext(buf_size);
    if (body_buf) {
        if (len > 0) {
            znx_strncpy(new_buf->end, readline_sm->line.data, len);
            new_buf->end += len;
        }
        znx_buf_free(body_buf);
    }

    request->body_buf = new_buf;
}


static znx_result_t
znx_http_read_request_trailers(znx_http_request_t *request)
{
    znx_http_core_conf_t        *cf;
    znx_buf_t                   *body_buf;
    znx_http_readline_sm_t      *readline_sm;
    size_t                      n;
    znx_result_t                res;
    znx_http_header_t           tmp;
    znx_http_conn_t             *hc;
    znx_http_header_t           *header;

    cf = request->cf;
    readline_sm = &request->readline_sm;
    hc = request->hc;

    for ( ;; ) {
        znx_http_try_switch_request_trailers_buf(request, cf->request_body_buf_size);

        body_buf = request->body_buf;
        if (body_buf->end - body_buf->pos == 0) {
            n = (size_t)(body_buf->data_end - body_buf->end);
            if (n == 0) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "no enough space to write request body trailers");
                ZNX_HTTP_LOG_FATAL(hc->hlog, "unreachable");
            }

            res = znx_http_conn_read(hc, body_buf->end, n);
            if (res == ZNX_AGAIN) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "read request trailers again");
                return ZNX_AGAIN;
            }

            if (res == 0) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "read request trailers EOS");
                return ZNX_HTTP_CLENT_CLOSE_IN_READ_TRAILERS;
            }

            if (res < 0) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "read request trailers failed, err: %d", res);
                return res;
            }

            body_buf->end += res;
        }

        // read line
        res = znx_http_readline(readline_sm, body_buf);
        if (res == ZNX_AGAIN) {
            continue;
        }

        if (res != ZNX_SUCCESS) {
            ZNX_HTTP_LOG_DEBUG(request->hc->hlog, "found big trailer header line");
            return ZNX_HTTP_INVALID_TRAILERS_HEADER;
        }


        // no trailers or read trailers done.
        if (request->trailers == NULL ||
            request->trailers->parse_trailers == request->trailers->total_trailers) {
            if (!znx_http_empty_line(readline_sm)) {
                return ZNX_HTTP_INVALID_TRAILERS_HEADER;
            }
            request->read_body_done = 1;
            return ZNX_SUCCESS;
        }

        bzero(&tmp, sizeof(znx_http_header_t));
        res = znx_http_parse_header_line(&request->readline_sm.line, &tmp);
        if (res != ZNX_SUCCESS) {
            return ZNX_HTTP_INVALID_TRAILERS_HEADER;
        }

        header = znx_pool_calloc(request->pool, sizeof(znx_http_header_t));
        header->key.data = znx_pool_pstr(request->pool, &tmp.key);
        header->key.len = tmp.key.len;
        header->val.data = znx_pool_pstr(request->pool, &tmp.val);
        header->val.len = tmp.val.len;

        znx_http_headers_add(&request->trailers->headers, header);
        request->trailers->parse_trailers++;
    }
}


static znx_result_t
znx_http_read_request_body_in_chunked_transfer(znx_http_request_t *request)
{
    znx_http_conn_t             *hc;
    znx_http_chunk_sm_t         *chunk_sm;
    znx_http_readline_sm_t      *readline_sm;
    znx_http_core_conf_t        *cf;
    znx_buf_t                   *body_buf;
    size_t                      n, todo;
    znx_http_chunk_state_t      state;
    znx_result_t                res;

    if (!request->chunk_sm) {
        znx_http_readline_sm_reset(&request->readline_sm);
        request->chunk_sm = znx_pool_calloc(request->pool, sizeof(znx_http_chunk_sm_t));
    }

    if (request->read_trailers) {
        return znx_http_read_request_trailers(request);
    }

    hc = request->hc;
    chunk_sm = request->chunk_sm;
    readline_sm = &request->readline_sm;
    cf = request->cf;

    for ( ;; ) {
        znx_http_try_switch_request_chunk_buf(request, cf->request_body_buf_size);

        body_buf = request->body_buf;
        if (body_buf->end - body_buf->pos == 0) {
            n = (size_t)(body_buf->data_end - body_buf->end);
            if (n == 0) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "no enough space to write request body");
                znx_http_try_flush_request_chunk_buf(request);
                return ZNX_AGAIN;
            }

            res = znx_http_conn_read(hc, body_buf->end, n);
            if (res == ZNX_AGAIN) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "read request body again");
                znx_http_try_flush_request_chunk_buf(request);
                return ZNX_AGAIN;
            }

            if (res == 0) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "read request body EOS");
                znx_http_try_flush_request_chunk_buf(request);
                return ZNX_HTTP_CLIENT_CLOSE_IN_READ_BODY;
            }

            if (res < 0) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "read request body failed, err: %d", res);
                znx_http_try_flush_request_chunk_buf(request);
                return res;
            }

            body_buf->end += res;
        }

        for ( ;; ) {
            state = chunk_sm->state;

            switch (state) {
            case SW_CHUNK_PRESTART:
                res = znx_http_readline(readline_sm, body_buf);
                break;

            case SW_CHUNK_START:
            case SW_CHUNK_SIZE:
                res = znx_http_chunk_size_parse(chunk_sm, &readline_sm->line);
                break;

            case SW_CHUNK_DATA:
                // consum data
                n = (size_t)(body_buf->end - body_buf->pos);
                todo = chunk_sm->remain;
                if (n > todo) {
                    n = todo;
                }
                body_buf->pos += n;
                chunk_sm->remain -= n;

                res = ZNX_AGAIN;
                if (chunk_sm->remain == 0) {
                    znx_http_try_flush_request_chunk_buf(request);
                    // to next step.
                    chunk_sm->state = SW_CHUNK_AFTER_DATA_CRCF;
                    res = ZNX_SUCCESS;
                }

                break;

            case SW_CHUNK_AFTER_DATA_CRCF:
                res = znx_http_readline(readline_sm, body_buf);
                break;

            default:
                ZNX_HTTP_LOG_FATAL(hc->hlog, "unreachable");
            }

            if (res == ZNX_SUCCESS) {
                switch (state) {
                case SW_CHUNK_PRESTART:
                    znx_http_chunk_sm_reset(chunk_sm);
                    chunk_sm->state = SW_CHUNK_START;
                    break;

                case SW_CHUNK_START:
                case SW_CHUNK_SIZE:
                    // use last_buf to record the starting position of the data.
                    // the last_buf is the shadow of body_buf.
                    request->last_buf->pos = request->body_buf->pos;

                    if (chunk_sm->size == 0) {
                        request->read_trailers = 1;
                        znx_http_readline_sm_reset(readline_sm);
                        return znx_http_read_request_trailers(request);
                    }

                    break;

                case SW_CHUNK_DATA:
                    znx_http_readline_sm_reset(readline_sm);
                    break;

                case SW_CHUNK_AFTER_DATA_CRCF:
                    if (!znx_http_empty_line(readline_sm)) {
                        return ZNX_HTTP_INVALID_CHUNK_LINE;
                    }

                    znx_http_chunk_sm_reset(chunk_sm);
                    znx_http_readline_sm_reset(readline_sm);
                    break;

                default:
                    ZNX_HTTP_LOG_FATAL(hc->hlog, "unreachable");
                }

                continue;
            }

            if (res == ZNX_AGAIN) {
                break;
            }

            // error
            return res;
        }
    }
}


static void
znx_http_try_flush_request_body_buf(znx_http_request_t *request)
{
    znx_buf_t               *body_buf;
    znx_buf_t               *last_buf;
    znx_buf_t               *tmp_buf;
    size_t                  n;

    body_buf = request->body_buf;
    last_buf = request->last_buf;

    if (!body_buf) {
        return;
    }

    if (last_buf == NULL ||
        last_buf->pos > body_buf->pos ||
        last_buf->pos < body_buf->start)
    {
        ZNX_HTTP_LOG_FATAL(request->hc->hlog, "unreachable, the last_buf is the shadow of body_buf");
    }

    n = (size_t)(body_buf->pos - last_buf->pos);

    if (n == 0) {
        return;
    }

    tmp_buf = znx_buf_alloc(body_buf->data, last_buf->pos, body_buf->pos);
    znx_buf_chain_enqueue(request->body_chain, tmp_buf, ZNX_FALSE);

    last_buf->pos = body_buf->pos;
}


static znx_result_t
znx_http_parse_after_body_line(znx_http_request_t *request)
{
    znx_buf_t       *tmp_buf;
    size_t          n;
    znx_result_t    res;

    // use chunk_sm to mark start_parse_after_body_line.
    if (!request->chunk_sm) {
        request->chunk_sm = (void *)(intptr_t)1;

        znx_http_readline_sm_reset(&request->readline_sm);
        // set max_line_size = 2.
        request->readline_sm.max_line_size = 2;

        if (request->body_buf == NULL) {
            request->body_buf = znx_buf_alloc_ext(64+ZNX_HTTP_MIN_WRITEABLE_SIZE);
            request->last_buf->pos = request->body_buf->pos;
        }

        if (request->body_buf->data_end - request->body_buf->pos < CRLF_LEN) {
            tmp_buf = znx_buf_alloc_ext(64+ZNX_HTTP_MIN_WRITEABLE_SIZE);
            n = (size_t)(request->body_buf->end - request->body_buf->pos);
            if (n > 0) {
                znx_strncpy(tmp_buf->end, request->body_buf->pos, n);
                request->body_buf->pos += n;
                tmp_buf->end += n;
            }

            znx_buf_free(request->body_buf);
            request->body_buf = tmp_buf;
            request->last_buf->pos = request->body_buf->pos;
        }

    }

    res = znx_http_readline(&request->readline_sm, request->body_buf);
    if (res == ZNX_SUCCESS) {
        if (znx_http_empty_line(&request->readline_sm)) {
            request->read_body_done = 1;
            return ZNX_SUCCESS;
        }
        return ZNX_HTTP_INVALID_AFTER_BODY_LINE;
    }

    // ZNX_AGAIN or ZNX_FAILED
    return res;
}


static void
znx_http_try_switch_request_body_buf(znx_http_request_t *request, size_t buf_size)
{
    znx_buf_t               *body_buf;
    znx_buf_t               *last_buf;
    znx_buf_t               *tmp_buf;
    znx_buf_t               *new_buf;
    size_t                  total_size, n;
    znx_http_core_conf_t    *cf;

    buf_size += ZNX_HTTP_MIN_WRITEABLE_SIZE;
    body_buf = request->body_buf;
    last_buf = request->last_buf;
    cf = request->cf;

    // there is unparsed data, no need switch buf.
    if (body_buf && body_buf->end != body_buf->pos) {
        return;
    }

    // avoid writing in a small space
    if (body_buf && body_buf->data_end - body_buf->end > ZNX_HTTP_MIN_WRITEABLE_SIZE) {
        return;
    }

    total_size = znx_buf_chain_total_bytes(request->body_chain);
    if (total_size >= cf->max_read_request_body_size_limit) {
        return;
    }

    new_buf = znx_buf_alloc_ext(buf_size);

    if (body_buf) {
        if (last_buf == NULL ||
            last_buf->pos > body_buf->pos ||
            last_buf->pos < body_buf->start)
        {
            ZNX_HTTP_LOG_FATAL(request->hc->hlog, "unreachabke, the last_buf is the shadow of body_buf");
        }

        n = (size_t)(body_buf->pos - last_buf->pos);
        if (n > 0) {
            tmp_buf = znx_buf_alloc(body_buf->data, last_buf->pos, body_buf->pos);
            znx_buf_chain_enqueue(request->body_chain, tmp_buf, ZNX_FALSE);
        }

        znx_buf_free(body_buf);
    }

    request->body_buf = new_buf;
    request->last_buf->pos = new_buf->pos;
}


static znx_result_t
znx_http_read_request_body_in_cl_transfer(znx_http_request_t *request)
{
    znx_buf_t               *body_buf;
    size_t                  n, consume;
    znx_result_t            res;
    znx_http_conn_t         *hc;
    znx_http_core_conf_t    *cf;

    hc = request->hc;
    cf = request->cf;

    for ( ;; ) {
        znx_http_try_switch_request_body_buf(request, cf->request_body_buf_size);

        body_buf = request->body_buf;
        if (body_buf->end - body_buf->pos == 0) {
            n = (size_t)(body_buf->data_end - body_buf->end);
            if (n == 0) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "no enough space to write request body");
                znx_http_try_flush_request_body_buf(request);
                return ZNX_AGAIN;
            }

            res = znx_http_conn_read(hc, body_buf->end, n);
            if (res == ZNX_AGAIN) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "read request body again");
                znx_http_try_flush_request_body_buf(request);
                return ZNX_AGAIN;
            }

            if (res == 0) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "read request body EOS");
                znx_http_try_flush_request_body_buf(request);
                return ZNX_HTTP_CLIENT_CLOSE_IN_READ_BODY;
            }

            if (res < 0) {
                ZNX_HTTP_LOG_DEBUG(hc->hlog, "read request body failed, err: %d", res);
                znx_http_try_flush_request_body_buf(request);
                return res;
            }

            body_buf->end += res;
        }

        // read after body line
        if (request->content_remain == 0) {
            znx_http_try_flush_request_body_buf(request);
            res = znx_http_parse_after_body_line(request);
            if (res == ZNX_AGAIN) {
                continue;
            }

            // ZNX_SUCCESS or other errors.
            return res;
        }

        consume = (size_t)(body_buf->end - body_buf->pos);
        if (consume > (size_t)(request->content_remain)) {
            consume = (size_t)request->content_remain;
        }

        request->content_remain -= consume;
        request->body_buf->pos += consume;
    }

    // unreachable
}


static znx_result_t
znx_http_do_read_request_body(znx_http_request_t *request)
{
    znx_http_conn_t         *hc;
    znx_result_t            res;
    hc = request->hc;

    if (hc->c->read.timedout) {
        ZNX_HTTP_LOG_DEBUG(hc->hlog, "read request body timedout");
        hc->c->read.timedout = 0;
        return ZNX_HTTP_READ_BODY_TIMEDOUT;
    }

    if (request->chunked) {
        res = znx_http_read_request_body_in_chunked_transfer(request);
    } else if (request->content_length >= 0) {
        res = znx_http_read_request_body_in_cl_transfer(request);
    } else {
        // assert(request->nobody);
        res = ZNX_SUCCESS;
        request->read_body_done = 1;
    }

    // read eagain, or read no enough space to write request body.
    if (res == ZNX_AGAIN) {
        return ZNX_AGAIN;
    }

    // ZNX_SUCCESS or other errors.
    return res;
}


static void
znx_http_read_request_body_handler(znx_http_conn_t *hc)
{
    znx_result_t            res;
    znx_http_request_t      *request;
    znx_http_core_conf_t    *cf;

    request = hc->data;
    cf = request->cf;

    // if res == ZNX_SUCCESS, this means request body read done.
    // if res == ZNX_AGAIN:
    // *1. the data may have been read.
    // *2. uneconsumed body data reaches the maximum value.
    // *3. reading operation encountered AGAIN.
    // other errors.
    res = znx_http_do_read_request_body(request);

    // in again scene, no body data, no callback needed
    if (res == ZNX_AGAIN &&
        znx_buf_chain_total_bytes(request->body_chain) == 0)
    {
        znx_event_loop_add_timer(&hc->c->read, cf->read_request_body_timeout_ms);
        znx_http_enable_read_request_body(request);
        return;
    }

    if (res != ZNX_AGAIN && res != ZNX_SUCCESS) {
        request->read_error = res;
    }

    znx_http_disable_read_request_body(request);
    request->handler(request, res);
}


void
znx_http_start_read_request_body(znx_http_request_t *request,
    znx_http_request_handler_t notify_handler, void *data)
{
    znx_http_core_conf_t        *cf;
    cf = request->cf;

    if (request->client_request) {
        ZNX_HTTP_LOG_FATAL(request->hc->hlog, "expect http server request");
    }

    if (!request->parse_rh_done) {
        ZNX_HTTP_LOG_FATAL(request->hc->hlog, "header has no been parsed");
    }

    if (request->start_read_body) {
        ZNX_HTTP_LOG_FATAL(request->hc->hlog, "repeat call start_read_body");
    }

    request->start_read_body = 1;

    if (request->read_error != ZNX_SUCCESS) {
        ZNX_HTTP_LOG_FATAL(request->hc->hlog, "an error has occurred in http request");
    }

    request->handler = notify_handler;
    request->data = data;
    request->hc->read_handler = znx_http_read_request_body_handler;
    request->readline_sm.max_line_size = cf->request_body_buf_size;
    znx_http_read_request_body_handler(request->hc);
}


void
znx_http_enable_read_request_body(znx_http_request_t *request)
{
    znx_http_conn_t                 *hc;

    hc = request->hc;

    if (request->client_request) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "expect http server request");
    }

    if (!request->start_read_body) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "must exec start read request body first");
    }

    if (request->read_error != ZNX_SUCCESS)
    {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "an error has occurred in http request");
    }

    if (request->read_body_done) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "http request body already read done");
    }

    if (!hc->read_active) {
        znx_http_conn_enable_read(hc);
    }

    // has data, schedule immediately.
    if (znx_http_conn_read_ready(hc) ||
        znx_buf_chain_total_bytes(request->body_chain) > 0 ||
        (request->body_buf && request->body_buf->pos != request->body_buf->end))
    {
        znx_event_loop_post_event(&hc->c->read);
    }
}


void
znx_http_disable_read_request_body(znx_http_request_t *request)
{
    znx_http_conn_t         *hc;

    hc = request->hc;

    if (request->client_request) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "expect http server request");
    }

    if (!request->start_read_body) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "must exec start read request body first");
    }

    znx_event_loop_del_timer(&hc->c->read);
    znx_http_conn_disable_read(hc);
}


znx_result_t
znx_http_seal_response_header(znx_http_request_t *request, znx_buf_t **header_buf_p)
{
    znx_http_response_t         *response;
    znx_str_t                   *http_version_str;
    znx_http_header_t           *header, *tmp;
    znx_http_conn_t             *hc;
    znx_buf_t                   *header_buf;
    znx_queue_t                 *q;
    size_t                      n, offset;

    response = request->response;
    hc = request->hc;

    if (proc_exit) {
        request->want_close = 1;
    }

    if (response->status_code == 0) {
        response->status_code = ZNX_HTTP_NOT_FOUND;
    }

    if (response->status_code < ZNX_HTTP_CONTINUE &&
        response->status_code > ZNX_HTTP_STATUS_CODE_END)
    {
        ZNX_HTTP_LOG_ERROR(request->hc->hlog, "invalid response status code %d", response->status_code);
        response->status_code = ZNX_HTTP_INTERNAL_SERVER_ERROR;
    }

    // 4xx, 5xx disable keepalive
    if (response->status_code >= ZNX_HTTP_BAD_REQUEST) {
        request->want_close = 1;
    }

    if (znx_http_method_is_head(&request->method)) {
        znx_http_headers_del(&response->headers, &znx_http_transfer_encoding_hdr_name);
        response->nobody = 1;
    }

    if (!znx_http_body_allowed_for_status(response->status_code)) {
        znx_http_headers_del(&response->headers, &znx_http_transfer_encoding_hdr_name);
        znx_http_headers_del(&response->headers, &znx_http_content_length_hdr_name);
        response->nobody = 1;
    }

    if (response->status_reason.len == 0) {
        response->status_reason.data =
            (u_char *)znx_http_get_response_reason(response->status_code);
        response->status_reason.len = znx_strlen(response->status_reason.data);
    }

    if (request->http_version == ZNX_HTTP_VERSION_20) {
        ZNX_HTTP_LOG_FATAL(request->hc->hlog, "no implements");
    } else if (request->http_version == ZNX_HTTP_VERSION_QUIC) {
        ZNX_HTTP_LOG_FATAL(request->hc->hlog, "no implements");
    } else if (request->http_version == ZNX_HTTP_VERSION_9) {
        ZNX_HTTP_LOG_FATAL(request->hc->hlog, "no implements");
    } else if (request->http_version == ZNX_HTTP_VERSION_10) {
        http_version_str = &znx_http_version_10;
    } else {
        http_version_str = &znx_http_version_11;
    }

    int64_t content_length = -1;
    header = znx_http_headers_get(&response->headers, &znx_http_content_length_hdr_name, 0);
    if (header != NULL) {
        if (!znx_str_to_int64(&header->val, &content_length) || content_length < 0) {
            ZNX_HTTP_LOG_ERROR(hc->hlog, "invalid content length header");
            return ZNX_HTTP_INVALID_CONTENT_LENGTH_HEADER;
        }

        response->content_length = content_length;
        response->content_remain = content_length;
    }

    header = znx_http_headers_get(&response->headers, &znx_http_transfer_encoding_hdr_name, 0);
    if (header != NULL) {
        if (header->val.len == ZNX_HTTP_CHUNKED_LEN &&
            znx_strncasecmp(header->val.data, ZNX_HTTP_CHUNKED, ZNX_HTTP_CHUNKED_LEN) == 0)
        {
            response->chunked = 1;
        } else if (header->val.len != ZNX_HTTP_IDENTITY_LEN &&
            znx_strncasecmp(header->val.data, ZNX_HTTP_IDENTITY, ZNX_HTTP_IDENTITY_LEN) == 0)
        {
            ZNX_HTTP_LOG_ERROR(hc->hlog, "transfer-encoding: %v not implemented", &header->val);
            return ZNX_HTTP_ERR_NOT_IMPLEMENTED;
        }

        if (request->http_version == ZNX_HTTP_VERSION_10) {
            response->chunked = 0;
            znx_http_headers_del(&response->headers, &znx_http_transfer_encoding_hdr_name);
        }
    }

    // if there is no Content-Length or chunked Transfer-Encoding on a response
    // and the status is not 1xx, 204 or 304, then the body is unbounded.
    // see RFC 7230, section 3.3.
    if (!response->nobody && response->content_length < 0 && !response->chunked) {
        if (request->http_version == ZNX_HTTP_VERSION_11) {
            header = znx_pool_calloc(request->pool, sizeof(znx_http_header_t));
            header->key.data = (u_char *)ZNX_HTTP_TRANSFER_ENCODING_HDR;
            header->key.len = ZNX_HTTP_TRANSFER_ENCODING_HDR_LEN;
            header->val.data = (u_char *)ZNX_HTTP_CHUNKED;
            header->val.len = ZNX_HTTP_CHUNKED_LEN;
            znx_http_headers_add(&response->headers, header);
            response->chunked = 1;
        } else {
            // unbounded body in http1.0
            request->want_close = 1;
        }
    }

    header = znx_http_headers_get(&response->headers, &znx_http_connection_hdr_name, 0);
    if (header != NULL) {
        if (header->val.len == ZNX_HTTP_CLOSE_LEN ||
            znx_strncmp(header->val.data, ZNX_HTTP_CLOSE, ZNX_HTTP_CLOSE_LEN) == 0)
        {
            request->want_close = 1;
        }

        if (request->want_close) {
            header->val.len = ZNX_HTTP_CLOSE_LEN;
            header->val.data = (u_char *)ZNX_HTTP_CLOSE;
        }
    } else {
        if (request->want_close && request->http_version == ZNX_HTTP_VERSION_11) {
            header = znx_pool_calloc(request->pool, sizeof(znx_http_request_t));
            header->key.data = (u_char *)ZNX_HTTP_CONNECTION_HDR;
            header->key.len = ZNX_HTTP_CONNECTION_HDR_LEN;
            header->val.data = (u_char *)ZNX_HTTP_CLOSE;
            header->val.len = ZNX_HTTP_CLOSE_LEN;
            znx_http_headers_add(&response->headers, header);
        }
    }

    znx_http_headers_del(&response->headers, &znx_http_trailer_hdr_name);
    if (response->chunked && response->trailers) {
        n = 0;
        offset = 0;
        q = znx_queue_head(&response->trailers->headers.head);
        while (q != &response->trailers->headers.head) {
            header = znx_queue_data(q, znx_http_header_t, queue);
            n += header->key.len + 1;
            response->trailers->total_trailers++;
            q = q->next;
        }

        if (n > 0) {
            tmp = znx_pool_calloc(request->pool, sizeof(znx_http_header_t));
            tmp->key.data = (u_char *)ZNX_HTTP_TRAILER_HDR;
            tmp->key.len = ZNX_HTTP_TRAILER_HDR_LEN;
            tmp->val.data = znx_pool_malloc(request->pool, n);

            q = znx_queue_head(&response->trailers->headers.head);
            while (q != &response->trailers->headers.head) {
                header = znx_queue_data(q, znx_http_header_t, queue);
                znx_strncpy(tmp->val.data + offset, header->key.data, header->key.len);
                offset += header->key.len;
                *(tmp->val.data + offset) = ',';
                offset += 1;
                q = q->next;
            }

            if (offset > 0) {
                offset--;
                tmp->val.len = offset;
            }

            if (tmp->val.len > 0) {
                znx_http_headers_add(&response->headers, tmp);
            }
        }
    }

    n = 0;

    // HTTP_VERSION_STR + ' ' + STATUS_CODE + ' ' + HTTP_REASON + CRLF
    // eg: HTTP/1.1 200 OK\r\n
    n += http_version_str->len + 1 + 3 + 1 + response->status_reason.len + CRLF_LEN;
    // HEADER_NAME_STR + ':' + ' ' + HEADER_VALUE_STR + CRLF
    // eg: Content-Length: 100\r\n
    q = znx_queue_head(&response->headers.head);
    while (q != &response->headers.head) {
        header = znx_queue_data(q, znx_http_header_t, queue);
        n += header->key.len + 1 + 1 + header->val.len + CRLF_LEN;
        q = q->next;
    }
    // CRLF
    n += CRLF_LEN;
    n += 32;

    header_buf = znx_buf_alloc_ext(n);
    *header_buf_p = header_buf;

    header_buf->end = znx_snprintf(header_buf->end,
        (size_t)(header_buf->data_end - header_buf->end),
        "%v %d %v\r\n",
        http_version_str, response->status_code, &response->status_reason);

    q = znx_queue_head(&response->headers.head);
    while (q != &response->headers.head) {
        header = znx_queue_data(q, znx_http_header_t, queue);
        header_buf->end = znx_snprintf(header_buf->end,
            (size_t)(header_buf->data_end - header_buf->end),
            "%v: %v\r\n", &header->key, &header->val);
        q = q->next;
    }

    znx_strncpy(header_buf->end, CRLF, CRLF_LEN);
    header_buf->end += CRLF_LEN;
    return ZNX_SUCCESS;
}

znx_result_t
znx_http_send_response_header(znx_http_request_t *request)
{
    znx_http_conn_t             *hc;
    znx_http_response_t         *response;
    znx_result_t                res;

    hc = request->hc;
    response = request->response;

    if (request->client_request) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "expect server request");
    }

    if (request->write_error != ZNX_SUCCESS)
    {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "an error has occurred in http response");
    }

    if (response->header_sent) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "the response header has been sent");
    }
    response->header_sent = 1;

    znx_buf_t   *header_buf = NULL;
    res = znx_http_seal_response_header(request, &header_buf);
    if (res != ZNX_SUCCESS) {
        request->write_error = res;
        return res;
    }

    znx_http_conn_start_io(hc);

    size_t n = (size_t)(header_buf->end - header_buf->pos);
    res = znx_http_conn_write(hc, header_buf->pos, n);
    if (res > 0 && (size_t)(res) == n) {
        header_buf->pos += res;
        znx_buf_free(header_buf);
        return ZNX_SUCCESS;
    }

    znx_buf_chain_enqueue(request->output_chain, header_buf, ZNX_FALSE);

    if (res == 0) {
        request->write_error = ZNX_HTTP_CLIENT_CLOSE_IN_SEND_RESPONSE;
        return ZNX_HTTP_CLIENT_CLOSE_IN_SEND_RESPONSE;
    }

    if (res < 0) {
        request->write_error = ZNX_HTTP_SEND_RESPONSE_FAILED;
        return ZNX_HTTP_SEND_RESPONSE_FAILED;
    }

    return ZNX_SUCCESS;
}


static void
znx_http_response_body_move_in_chunked_transfer_internal(znx_http_request_t *request)
{
    znx_http_response_t         *response;
    size_t                      todo, consume, n;
    znx_buf_t                   *tmp_buf, *body_buf;

    response = request->response;

    // last chunk buf already write output_chain.
    if (response->write_body_done) {
        return;
    }

    todo = znx_buf_chain_total_bytes(response->body_chain);
    if (todo == 0) {
        return;
    }

    // add chunked size buf before moveing response body.
    tmp_buf = znx_buf_alloc_ext(128);
    tmp_buf->end = znx_snprintf(tmp_buf->end, 128, "%xd\r\n", todo);

    znx_buf_chain_enqueue(request->output_chain, tmp_buf, ZNX_FALSE);

    consume = 0;
    // move response body from response->body_chain to output_chain
    body_buf = znx_buf_chain_first(response->body_chain);
    while (body_buf != NULL) {
        n = (size_t)(body_buf->end - body_buf->pos);
        if (n > 0) {
            tmp_buf = znx_buf_alloc(body_buf->data, body_buf->pos, body_buf->end);
            znx_buf_chain_enqueue(request->output_chain, tmp_buf, ZNX_FALSE);
            consume += n;
        }

        body_buf = znx_buf_chain_next(response->body_chain, body_buf);
    }

    znx_buf_chain_consume(response->body_chain, consume);

    // add chunk data crlf.
    tmp_buf = znx_buf_alloc_ext(128);
    znx_strncpy(tmp_buf->end, CRLF, CRLF_LEN);
    tmp_buf->end += CRLF_LEN;
    znx_buf_chain_enqueue(request->output_chain, tmp_buf, ZNX_FALSE);
}


static void
znx_http_response_body_move_in_chunked_transfer(znx_http_request_t *request)
{
    znx_http_response_t     *response;
    size_t                  n;
    znx_buf_t               *last_buf;
    znx_queue_t             *q;
    znx_http_header_t       *header;

    response = request->response;

    znx_http_response_body_move_in_chunked_transfer_internal(request);

    // body is not finished.
    if (!response->last_body_flag) {
        return;
    }

    // last chunk buf already write output_chain.
    if (response->write_body_done) {
        return;
    }

    response->write_body_done = 1;

    // append last chunk buf & trailers to output chain
    n = 128;
    if (response->trailers) {
        q = znx_queue_head(&response->trailers->headers.head);
        while (q != &response->trailers->headers.head) {
            header = znx_queue_data(q, znx_http_header_t, queue);
            n += header->key.len + 1 + 1 + header->val.len + CRLF_LEN;
            q = q->next;
        }
    }

    last_buf = znx_buf_alloc_ext(n);
    znx_strncpy(last_buf->end, "0\r\n", 3);
    last_buf->end += 3;

    if (response->trailers) {
        q = znx_queue_head(&response->trailers->headers.head);
        while (q != &response->trailers->headers.head) {
            header = znx_queue_data(q, znx_http_header_t, queue);
            znx_strncpy(last_buf->end, header->key.data, header->key.len);
            last_buf->end += header->key.len;
            znx_strncpy(last_buf->end, ": ", 2);
            last_buf->end += 2;
            znx_strncpy(last_buf->end, header->val.data, header->val.len);
            last_buf->end += header->val.len;
            znx_strncpy(last_buf->end, CRLF, CRLF_LEN);
            last_buf->end += CRLF_LEN;
            q = q->next;
        }
    }

    znx_strncpy(last_buf->end, CRLF, CRLF_LEN);
    last_buf->end += CRLF_LEN;
    znx_buf_chain_enqueue(request->output_chain, last_buf, ZNX_FALSE);
}


static znx_result_t
znx_http_send_response_body_in_chunked_transfer(znx_http_request_t *request)
{
    znx_http_response_t         *response;
    znx_buf_t                   *buf;
    size_t                      n, todo, niov;
    znx_http_conn_t             *hc;
    znx_iovec_t                 tiovec[ZNX_HTTP_MAX_WRITE_IOV];
    znx_result_t                res;

    response = request->response;
    hc = request->hc;

    znx_http_response_body_move_in_chunked_transfer(request);

    if (znx_buf_chain_total_bytes(request->output_chain) == 0) {
        if (response->last_body_flag) {
            response->send_body_done = 1;
            return ZNX_SUCCESS;
        }
        return ZNX_AGAIN;
    }

    for ( ;; ) {
        todo = 0;
        niov = 0;

        buf = znx_buf_chain_first(request->output_chain);
        while (niov < ZNX_HTTP_MAX_WRITE_IOV && buf) {
            n = (size_t)(buf->end - buf->pos);
            if (n == 0) {
                buf = znx_buf_chain_next(request->output_chain, buf);
                continue;
            }

            tiovec[niov].iov_base = buf->pos;
            tiovec[niov].iov_len = n;
            niov++;
            todo += n;

            buf = znx_buf_chain_next(request->output_chain, buf);
        }

        if (todo == 0) {
            if (response->last_body_flag) {
                response->send_body_done = 1;
                return ZNX_SUCCESS;
            }

            return ZNX_AGAIN;
        }

        if (niov == 1) {
            res = znx_http_conn_write(hc, tiovec[0].iov_base, tiovec[0].iov_len);
        } else {
            res = znx_http_conn_writev(hc, tiovec, (int)niov);
        }

        if (res == ZNX_AGAIN) {
            ZNX_HTTP_LOG_DEBUG(hc->hlog, "send response body eagain");
            return ZNX_AGAIN;
        }

        if (res == 0) {
            ZNX_HTTP_LOG_DEBUG(hc->hlog, "client close read in send response body");
            return ZNX_HTTP_CLIENT_CLOSE_IN_SEND_RESPONSE;
        }

        if (res < 0) {
            ZNX_HTTP_LOG_DEBUG(hc->hlog, "send response body failed, err: %d", res);
            return res;
        }

        znx_buf_chain_consume(request->output_chain, (size_t)res);
    }
}


static void
znx_http_response_body_move_in_cl_transfer_internal(znx_http_request_t *request)
{
    znx_http_response_t         *response;
    znx_buf_t                   *body_buf;
    size_t                      consume, n;
    znx_buf_t                   *tmp_buf;

    response = request->response;

    // last CRLF is written.
    // use write_body_done mark whether last CRLF is written.
    if (response->write_body_done) {
        return;
    }

    // move response body from request->body_chain to request->output_chain.

    consume = 0;
    body_buf = znx_buf_chain_first(response->body_chain);
    while (body_buf != NULL && response->content_remain > 0) {
        n = (size_t)(body_buf->end - body_buf->pos);
        if (n > (size_t)response->content_remain) {
            n = (size_t)response->content_remain;
        }

        if (n > 0) {
            tmp_buf = znx_buf_alloc(body_buf->data, body_buf->pos, body_buf->pos + n);
            znx_buf_chain_enqueue(request->output_chain, tmp_buf, ZNX_FALSE);
            consume += n;
            response->content_remain -= n;

        }

        body_buf = znx_buf_chain_next(response->body_chain, body_buf);
    }
    znx_buf_chain_consume(response->body_chain, consume);
}


static void
znx_http_response_body_move_in_cl_transfer(znx_http_request_t *request)
{
    znx_http_response_t         *response;
    znx_buf_t                   *last_buf;

    response = request->response;

    znx_http_response_body_move_in_cl_transfer_internal(request);

    if (response->content_remain > 0) {
        return;
    }

    if (response->write_body_done) {
        return;
    }

    response->write_body_done = 1;

    last_buf = znx_buf_alloc_ext(128);
    znx_strncpy(last_buf->end, CRLF, CRLF_LEN);
    last_buf->end += CRLF_LEN;
    znx_buf_chain_enqueue(request->output_chain, last_buf, ZNX_FALSE);
}


static znx_result_t
znx_http_send_response_body_in_cl_transfer(znx_http_request_t *request)
{
    znx_http_response_t         *response;
    znx_http_conn_t             *hc;
    znx_buf_t                   *buf;
    size_t                      n, todo, niov;
    znx_iovec_t                 tiovec[ZNX_HTTP_MAX_WRITE_IOV];
    znx_result_t                res;

    response = request->response;
    hc = request->hc;

    znx_http_response_body_move_in_cl_transfer(request);

    if (znx_buf_chain_total_bytes(request->output_chain) == 0) {
        if (response->content_remain == 0) {
            response->send_body_done = 1;
            return ZNX_SUCCESS;
        }
        return ZNX_AGAIN;
    }

    for ( ;; ) {
        todo = 0;
        niov = 0;

        buf = znx_buf_chain_first(request->output_chain);
        while (niov < ZNX_HTTP_MAX_WRITE_IOV && buf) {
            n = (size_t)(buf->end - buf->pos);
            if (n == 0) {
                buf = znx_buf_chain_next(request->output_chain, buf);
                continue;
            }

            tiovec[niov].iov_base = buf->pos;
            tiovec[niov].iov_len = n;
            niov++;
            todo += n;

            buf = znx_buf_chain_next(request->output_chain, buf);
        }

        if (todo == 0) {
            if (response->content_remain == 0) {
                response->send_body_done = 1;
                return ZNX_SUCCESS;
            }

            return ZNX_AGAIN;
        }

        if (niov == 1) {
            res = znx_http_conn_write(hc, tiovec[0].iov_base, tiovec[0].iov_len);
        } else {
            res = znx_http_conn_writev(hc, tiovec, (int)niov);
        }

        if (res == ZNX_AGAIN) {
            ZNX_HTTP_LOG_DEBUG(hc->hlog, "send response body eagain");
            return ZNX_AGAIN;
        }

        if (res == 0) {
            ZNX_HTTP_LOG_DEBUG(hc->hlog, "client close read in send response body");
            return ZNX_HTTP_CLIENT_CLOSE_IN_SEND_RESPONSE;
        }

        if (res < 0) {
            ZNX_HTTP_LOG_DEBUG(hc->hlog, "send response body failed, err: %d", res);
            return res;
        }

        znx_buf_chain_consume(request->output_chain, (size_t)res);
    }
}


static void
znx_http_response_body_move_in_unbounded_body_transfer(znx_http_request_t *request)
{
    znx_http_response_t         *response;
    znx_buf_t                   *body_buf;
    size_t                      consume, n;
    znx_buf_t                   *tmp_buf;

    response = request->response;

    consume = 0;
    body_buf = znx_buf_chain_first(response->body_chain);
    while (body_buf != NULL) {
        n = (size_t)(body_buf->end - body_buf->pos);
        if (n > 0) {
            tmp_buf = znx_buf_alloc(body_buf->data, body_buf->pos, body_buf->pos + n);
            znx_buf_chain_enqueue(request->output_chain, tmp_buf, ZNX_FALSE);
            consume += n;
        }

        body_buf = znx_buf_chain_next(response->body_chain, body_buf);
    }
    znx_buf_chain_consume(response->body_chain, consume);
}


static znx_result_t
znx_http_send_response_in_unbounded_body_transfer(znx_http_request_t *request)
{
    znx_http_response_t         *response;
    znx_http_conn_t             *hc;
    znx_buf_t                   *buf;
    size_t                      n, todo, niov;
    znx_iovec_t                 tiovec[ZNX_HTTP_MAX_WRITE_IOV];
    znx_result_t                res;

    response = request->response;
    hc = request->hc;

    znx_http_response_body_move_in_unbounded_body_transfer(request);

    if (znx_buf_chain_total_bytes(request->output_chain) == 0) {
        if (response->last_body_flag) {
            response->send_body_done = 1;
            return ZNX_SUCCESS;
        }
        return ZNX_AGAIN;
    }

    for ( ;; ) {
        todo = 0;
        niov = 0;

        buf = znx_buf_chain_first(request->output_chain);
        while (niov < ZNX_HTTP_MAX_WRITE_IOV && buf) {
            n = (size_t)(buf->end - buf->pos);
            if (n == 0) {
                buf = znx_buf_chain_next(request->output_chain, buf);
                continue;
            }

            tiovec[niov].iov_base = buf->pos;
            tiovec[niov].iov_len = n;
            niov++;
            todo += n;

            buf = znx_buf_chain_next(request->output_chain, buf);
        }

        if (todo == 0) {
            if (response->last_body_flag) {
                response->send_body_done = 1;
                return ZNX_SUCCESS;
            }
            return ZNX_AGAIN;
        }

        if (niov == 1) {
            res = znx_http_conn_write(hc, tiovec[0].iov_base, tiovec[0].iov_len);
        } else {
            res = znx_http_conn_writev(hc, tiovec, (int)niov);
        }

        if (res == ZNX_AGAIN) {
            ZNX_HTTP_LOG_DEBUG(hc->hlog, "send response body eagain");
            return ZNX_AGAIN;
        }

        if (res == 0) {
            ZNX_HTTP_LOG_DEBUG(hc->hlog, "client close read in send response body");
            return ZNX_HTTP_CLIENT_CLOSE_IN_SEND_RESPONSE;
        }

        if (res < 0) {
            ZNX_HTTP_LOG_DEBUG(hc->hlog, "send response body failed, err: %d", res);
            return res;
        }

        znx_buf_chain_consume(request->output_chain, (size_t)res);
    }
}


static znx_result_t
znx_http_do_send_response_body(znx_http_request_t *request)
{
    znx_http_conn_t         *hc;
    znx_http_response_t     *response;

    hc = request->hc;
    response = request->response;

    if (hc->c->write.timedout) {
        ZNX_HTTP_LOG_DEBUG(hc->hlog, "send response body timedout");
        hc->c->write.timedout = 0;
        return ZNX_HTTP_WRITE_BODY_TIMEDOUT;
    }

    if (request->http_version == ZNX_HTTP_VERSION_20) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "no implements");
        return ZNX_HTTP_ERR_NOT_IMPLEMENTED;
    } else if (request->http_version == ZNX_HTTP_VERSION_QUIC) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "no implements");
        return ZNX_HTTP_ERR_NOT_IMPLEMENTED;
    } else if (request->http_version == ZNX_HTTP_VERSION_9) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "no implements");
        return ZNX_HTTP_ERR_NOT_IMPLEMENTED;
    }

    if (response->nobody) {
        response->send_body_done = 1;
        return ZNX_SUCCESS;
    }

    if (response->chunked) {
        return znx_http_send_response_body_in_chunked_transfer(request);
    }

    if (response->content_length >= 0) {
        return znx_http_send_response_body_in_cl_transfer(request);
    }

    return znx_http_send_response_in_unbounded_body_transfer(request);
}


static void
znx_http_send_response_body_handler(znx_http_conn_t *hc)
{
    znx_result_t            res;
    znx_http_request_t      *request;
    znx_http_response_t     *response;
    znx_http_core_conf_t    *cf;

    request = hc->data;
    response = request->response;
    cf = request->cf;

    res = znx_http_do_send_response_body(request);
    if (res == ZNX_AGAIN &&
        znx_buf_chain_total_bytes(request->output_chain) != 0)
    {
        znx_event_loop_add_timer(&hc->c->write, cf->write_response_body_timeout_ms);
        znx_http_enable_send_response_body(request);
        return;
    }

    if (res != ZNX_AGAIN && res != ZNX_SUCCESS) {
        request->write_error = res;
    }

    znx_http_disable_send_response_body(request);
    response->handler(request, res);
}


void
znx_http_start_send_response_body(znx_http_request_t *request,
    znx_http_request_handler_t notify_handler, void *data)
{
    znx_http_response_t         *response;
    znx_http_conn_t             *hc;

    hc = request->hc;
    response = request->response;

    if (request->client_request) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "expect server request");
    }

    if (!response->header_sent) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "response header no sent");
    }

    if (response->start_send_body) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "repeat call start_send_body");
    }

    response->start_send_body = 1;

    if (request->write_error)
    {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "an error has occurred in http response");
    }

    response->handler = notify_handler;
    response->data = data;

    if (response->nobody) {
        response->send_body_done = 1;
        notify_handler(request, ZNX_SUCCESS);
        return;
    }

    hc->write_handler = znx_http_send_response_body_handler;
    znx_http_send_response_body_handler(hc);
}


void
znx_http_enable_send_response_body(znx_http_request_t *request)
{
    znx_http_conn_t                 *hc;
    znx_http_response_t             *response;

    hc = request->hc;
    response = request->response;

    if (request->client_request) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "expect http server request");
    }

    if (!response->start_send_body) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "must exec start_send_response_body first");
    }

    if (request->write_error != ZNX_SUCCESS)
    {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "an error has occurred in http response");
    }

    if (response->send_body_done) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "the http response body already read done");
    }

    if (!hc->write_active) {
        znx_http_conn_enable_write(hc);
    }

    if (!znx_http_conn_write_ready(hc)) {
        return;
    }

    if (znx_buf_chain_total_bytes(request->output_chain) > 0 ||
        znx_buf_chain_total_bytes(response->body_chain) > 0)
    {
        znx_event_loop_post_event(&hc->c->write);
    }
}


void
znx_http_set_send_response_body_done(znx_http_request_t *request)
{
    request->response->last_body_flag = 1;
}


void
znx_http_disable_send_response_body(znx_http_request_t *request)
{
    znx_http_conn_t                 *hc;
    znx_http_response_t             *response;

    hc = request->hc;
    response = request->response;

    if (request->client_request) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "expect http server request");
    }

    if (!response->start_send_body) {
        ZNX_HTTP_LOG_FATAL(hc->hlog, "must exec start_send_response_body first");
    }

    znx_event_loop_del_timer(&hc->c->write);
    znx_http_conn_disable_write(hc);
}


// see RFC 7230, section 3.3.
znx_bool_t
znx_http_body_allowed_for_status(int status_code)
{
    if (status_code >= 100 && status_code <= 199) {
        return ZNX_FALSE;
    }

    if (status_code == 204) {
        return ZNX_FALSE;
    }

    if (status_code == 304) {
        return ZNX_FALSE;
    }

    return ZNX_TRUE;
}


znx_http_request_t *
znx_http_request_create(znx_pool_t *pool,
    znx_http_conn_t *hc, znx_bool_t client_request)
{
    znx_http_request_t      *request;
    znx_http_response_t     *response;

    request = znx_pool_calloc(pool, sizeof(znx_http_request_t));
    response = znx_pool_calloc(pool, sizeof(znx_http_response_t));

    request->response = response;
    response->request = request;
    request->hc = hc;
    hc->data = request;
    request->pool = pool;
    request->client_request = client_request;

    znx_http_headers_init(&request->headers);
    znx_http_headers_init(&response->headers);
    request->content_length = -1;
    response->content_length = -1;

    request->output_chain = znx_buf_chain_alloc();
    request->body_chain = znx_buf_chain_alloc();
    response->body_chain = znx_buf_chain_alloc();

    if (client_request) {
        return request;
    }

    request->header_chain = znx_buf_chain_alloc();

    if (hc->pre_buf) {
        znx_buf_chain_enqueue(request->header_chain, hc->pre_buf, ZNX_FALSE);
        request->last_buf = hc->pre_buf;
        hc->pre_buf = NULL;
    }

    return request;
}


void
znx_http_request_destroy(znx_http_request_t *request)
{
    znx_http_response_t     *response;
    response = request->response;

    if (request->header_chain) {
        znx_buf_chain_free(request->header_chain);
        request->header_chain = NULL;
    }

    if (request->body_chain) {
        znx_buf_chain_free(request->body_chain);
        request->body_chain = NULL;
    }

    if (request->body_buf) {
        znx_buf_free(request->body_buf);
        request->body_buf = NULL;
    }

    if (request->output_chain) {
        znx_buf_chain_free(request->output_chain);
        request->output_chain = NULL;
    }

    if (response->body_chain) {
        znx_buf_chain_free(response->body_chain);
        response->body_chain = NULL;
    }
}


void
znx_http_request_dump(znx_http_request_t *request)
{
    ZNX_HTTP_LOG_DEBUG(request->hc->hlog, "################################");
    ZNX_HTTP_LOG_DEBUG(request->hc->hlog, "method: %v", &request->method);
    ZNX_HTTP_LOG_DEBUG(request->hc->hlog, "uri: %v", &request->uri);
    ZNX_HTTP_LOG_DEBUG(request->hc->hlog, "args: %v", &request->args);
    ZNX_HTTP_LOG_DEBUG(request->hc->hlog, "request_uri: %v", &request->request_uri);
    ZNX_HTTP_LOG_DEBUG(request->hc->hlog, "http_version: %d", request->http_version);

    znx_queue_t         *q;
    znx_http_header_t   *header;
    q = znx_queue_head(&request->headers.head);
    while (q != &request->headers.head) {
        header = znx_queue_data(q, znx_http_header_t, queue);
        q = q->next;
        ZNX_HTTP_LOG_DEBUG(request->hc->hlog, "#%v: %v#", &header->key, &header->val);
    }
    
    ZNX_HTTP_LOG_DEBUG(request->hc->hlog, "################################");
}

